const std = @import("std");
const c_lib = @cImport({
    @cInclude("pthread.h");
    @cInclude("sys/fcntl.h");
    @cInclude("signal.h");
    @cInclude("stdint.h");
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("string.h");
    @cInclude("unistd.h");
});
const netdata = @cImport({
    @cInclude("appconfig.h");
    @cInclude("web_server.h");
    @cInclude("rrd.h");
    @cInclude("log.h");
    @cInclude("rrd2json.h");
});

const Context = struct {
    web_server_thread: c_lib.pthread_t,
    chart_table: std.AutoHashMap([*:0]const u8, ChartAttr),
};

const ChartAttr = struct {};

var context: Context = undefined;

pub fn init(allocator: std.mem.Allocator) !void {
    context = Context{
        .web_server_thread = undefined,
        .chart_table = std.AutoHashMap([*:0]const u8, ChartAttr).init(allocator),
    };

    netdata.listen_port = 49999;
    netdata.rrd_default_history_entries = (7 * 24 * 3600);
    netdata.access_log_syslog = 0;
    netdata.error_log_syslog = 0;
    netdata.output_log_syslog = 0;

    var buffer = allocator.alloc(u8, 512) catch @panic("OOM");
    defer allocator.free(buffer);
    var err = c_lib.gethostname(&buffer[0], buffer.len);
    if (err != 0) {
        return error.gethostname;
    }
    std.log.info("\nhostname: {s}\n", .{buffer[0..c_lib.strlen(&buffer[0])]});
    netdata.hostname = c_lib.strdup(&buffer[0]);
    _ = netdata.config_set_default("global", "web files directory", "/data/workspace/netdata-zig/lib/netdata/web_res");
}

pub fn start() !void {
    std.log.info("start web server", .{});
    _ = c_lib.signal(c_lib.SIGPIPE, c_lib.SIG_IGN);
    var err = c_lib.pthread_create(
        &context.web_server_thread,
        null,
        netdata.socket_listen_main,
        null,
    );
    if (err != 0) {
        return error.pthread_create;
    }
}

pub fn stop() !void {
    std.log.info("stop web server", .{});
    _ = c_lib.usleep(100000);
    var err = c_lib.pthread_cancel(context.web_server_thread);
    if (err != 0) {
        return error.pthread_cancel;
    }
    err = c_lib.pthread_join(context.web_server_thread, null);
    if (err != 0) {
        return error.pthread_join;
    }
    context.chart_table.deinit();
}

pub fn addChart(family: [*:0]const u8, id: [*:0]const u8, units: [*:0]const u8, priority: i32, update_interval: i32, chart_type: i32) !void {
    _ = netdata.rrdset_create(@as([*c]const u8, @ptrCast(family)), @as([*c]const u8, @ptrCast(id)), null, @as([*c]const u8, @ptrCast(id)), @as([*c]const u8, @ptrCast(id)), @as([*c]const u8, @ptrCast(family)), @as([*c]const u8, @ptrCast(units)), priority, update_interval, chart_type);
    try context.chart_table.put(id, ChartAttr{});
}

pub fn updateDimension(st: *netdata.RRDSET, id: [*:0]const u8, multiplier: i64, divisor: i64, algorithm: i32, value: i64) !void {
    var rd = netdata.rrddim_find(st, @as([*c]const u8, @ptrCast(id)));
    if (rd == null) {
        rd = netdata.rrddim_add(st, @as([*c]const u8, @ptrCast(id)), null, multiplier, divisor, algorithm);
        std.log.info("updateDimension1", .{});
    }
    if (rd != null) {
        _ = netdata.rrddim_set_by_pointer(st, rd, value);
        std.log.info("updateDimension2", .{});
    }
}

test "init, start, stop" {
    try init(std.testing.allocator);
    try start();
    try addChart("System", "cpu", "percentage", 10, 1, netdata.RRDSET_TYPE_LINE);
    try addChart("System", "memory", "GB", 10, 1, netdata.RRDSET_TYPE_LINE);
    var it = context.chart_table.iterator();
    while (it.next()) |kv| {
        std.debug.print("{s} {}\n", .{ kv.key_ptr.*, kv.value_ptr.* });
    }

    var st = netdata.rrdset_find_bytype("System", "cpu");
    try std.testing.expect(st != null);
    netdata.rrdset_next(st);
    try updateDimension(st, "user", 1, 1, netdata.RRDDIM_ABSOLUTE, 1);
    try updateDimension(st, "sys", 1, 1, netdata.RRDDIM_ABSOLUTE, 2);
    try updateDimension(st, "idle", 1, 1, netdata.RRDDIM_ABSOLUTE, 30);
    _ = netdata.rrdset_done(st);

    try stop();
    try std.testing.expectEqual(netdata.listen_port, 49999);

    var buf: [4]u8 = undefined;
    std.debug.print("buf_slice \"{p} {p} {p}\"\n", .{ &buf[0], &buf[1], &buf.len });
}

test "json parse with strings" {
    const User = struct { name: []u8, age: u16 };

    const parsed = try std.json.parseFromSlice(
        User,
        std.testing.allocator,
        \\{ "name": "Joe", "age": 25 }
    ,
        .{},
    );
    defer parsed.deinit();

    const user = parsed.value;
    std.debug.print("user: {}\n", .{user});

    try std.testing.expectEqualSlices(u8, user.name, "Joe");
    try std.testing.expect(user.age == 25);
}
